/* Copyright (c) 2022-2023, Arm Limited and Contributors. All rights reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

#include "iotsdk/nx_driver_cmsis_eth.h"

#include "Driver_ETH_MAC.h"
#include "Driver_ETH_PHY.h"

#include "nx_arp.h"
#include "nx_ip.h"
#include "nx_rarp.h"
#include "tx_port.h"

#include <assert.h>
#include <stdbool.h>

#ifndef ETH_DRV_NUM
#define ETH_DRV_NUM 0
#endif

#ifndef ETH_DRV_MTU_SIZE
/* Dst(6) + Src(6) + 802.1Q tag(4) + type/length(2) + Data(1500)*/
#define ETH_DRV_MTU_SIZE 1518
#endif

/* Ethernet MAC & PHY Driver */
extern ARM_DRIVER_ETH_MAC ARM_Driver_ETH_MAC_(ETH_DRV_NUM);
extern ARM_DRIVER_ETH_PHY ARM_Driver_ETH_PHY_(ETH_DRV_NUM);

#define ETHER_PACKET_TYPE_IP   0x0800
#define ETHER_PACKET_TYPE_IPV6 0x86dd
#define ETHER_PACKET_TYPE_ARP  0x0806
#define ETHER_PACKET_TYPE_RARP 0x8035

#define ETHERNET_FRAME_SIZE 14

#define ETHERNET_ETHER_TYPE_MSB_POS 12U
#define ETHERNET_ETHER_TYPE_LSB_POS 13U

#define ETHERNET_FRAME_ALIGNMENT_BYTES_SIZE 2U

#define ARM_ETH_MAC_ADDR_EQ(A, B)                                                                     \
    (A.b[0] == B.b[0] && A.b[1] == B.b[1] && A.b[2] == B.b[2] && A.b[3] == B.b[3] && A.b[4] == B.b[4] \
     && A.b[5] == B.b[5])

enum {
    NX_DRIVER_STATE_NOT_INITIALIZED = 1,
    NX_DRIVER_STATE_INITIALIZE_FAILED = 2,
    NX_DRIVER_STATE_INITIALIZED = 3,
    NX_DRIVER_STATE_LINK_ENABLED = 4
};

#define DEFERRED_EVENT_PACKET_RECEIVED (1 << 0)

typedef struct nx_driver_s {
    NX_INTERFACE *interface; /* Define the driver interface association.  */
    NX_IP *ip;               /* NetX IP instance that this driver is attached to.  */
} nx_driver_t;

typedef struct device_driver_s {
    ARM_DRIVER_ETH_MAC *mac;
    ARM_DRIVER_ETH_PHY *phy;
    ARM_ETH_LINK_STATE link;
    bool event_rx_frame;
    bool phy_ok;
    size_t mtu;
    ARM_ETH_MAC_ADDR mac_address;
    ARM_ETH_MAC_ADDR multicast_groups[NX_MAX_MULTICAST_GROUPS];
    NX_PACKET *outgoing_packet;
    NX_PACKET_POOL *incomming_packet_pool;
    nx_driver_t *nx_driver;
    uint8_t multicast_group_cnt;
    uint8_t deferred_event;
    uint8_t state;
} device_driver_t;

static void nx_driver_cmsis_ip_link_fsm_impl(NX_IP_DRIVER *nx_ip_driver, device_driver_t *device);

static void nx_link_interface_attach(NX_IP_DRIVER *nx_ip_driver, device_driver_t *device);
static void nx_link_initialize(NX_IP_DRIVER *nx_ip_driver, device_driver_t *device);
static void nx_link_enable(NX_IP_DRIVER *nx_ip_driver, device_driver_t *device);
static void nx_link_disable(NX_IP_DRIVER *nx_ip_driver, device_driver_t *device);
static void nx_link_packet_send(NX_IP_DRIVER *nx_ip_driver, device_driver_t *device);
static void nx_link_multicast_join(NX_IP_DRIVER *nx_ip_driver, device_driver_t *device);
static void nx_link_multicast_leave(NX_IP_DRIVER *nx_ip_driver, device_driver_t *device);
static void nx_link_get_status(NX_IP_DRIVER *nx_ip_driver, device_driver_t *device);
static void nx_link_deferred_processing(NX_IP_DRIVER *nx_ip_driver, device_driver_t *device);
static bool transfer_to_netx(NX_IP_DRIVER *nx_ip_driver, NX_PACKET *incoming_packet);
static inline void remove_nx_packet_ethernet_header(NX_PACKET *packet);

static void add_ethernet_header(NX_IP_DRIVER *nx_ip_driver, uint32_t *ethernet_frame_ptr);
static UINT hardware_initialize(device_driver_t *device);
static void hardware_check_link(device_driver_t *device);
static UINT hardware_enable(device_driver_t *device);
static UINT hardware_disable(device_driver_t *device);
static UINT hardware_multicast_join(NX_IP_DRIVER *nx_ip_driver, device_driver_t *device);
static UINT hardware_multicast_leave(NX_IP_DRIVER *nx_ip_driver, device_driver_t *device);
static UINT hardware_send_packet(NX_PACKET *outgoing_packet, device_driver_t *device);
static UINT hardware_receive_packet(NX_IP_DRIVER *nx_ip_driver, device_driver_t *device);
static void on_event(uint32_t event);

static nx_driver_t gs_nx_driver = {0};
static device_driver_t gs_device = {.nx_driver = &gs_nx_driver};

void nx_driver_cmsis_ip_link_fsm(NX_IP_DRIVER *nx_ip_driver)
{
    nx_driver_cmsis_ip_link_fsm_impl(nx_ip_driver, &gs_device);
}

static void nx_driver_cmsis_ip_link_fsm_impl(NX_IP_DRIVER *nx_ip_driver, device_driver_t *device)
{
    nx_ip_driver->nx_ip_driver_status = NX_SUCCESS;

    switch (nx_ip_driver->nx_ip_driver_command) {
        case NX_LINK_INTERFACE_ATTACH:
            nx_link_interface_attach(nx_ip_driver, device);
            break;
        case NX_LINK_INITIALIZE:
            nx_link_initialize(nx_ip_driver, device);
            break;
        case NX_LINK_ENABLE:
            nx_link_enable(nx_ip_driver, device);
            break;
        case NX_LINK_DISABLE:
            nx_link_disable(nx_ip_driver, device);
            break;
        case NX_LINK_GET_STATUS:
            nx_link_get_status(nx_ip_driver, device);
            break;
        case NX_LINK_ARP_SEND:
        case NX_LINK_ARP_RESPONSE_SEND:
        case NX_LINK_PACKET_BROADCAST:
        case NX_LINK_RARP_SEND:
        case NX_LINK_PACKET_SEND:
            nx_link_packet_send(nx_ip_driver, device);
            break;
        case NX_LINK_MULTICAST_JOIN:
            nx_link_multicast_join(nx_ip_driver, device);
            break;
        case NX_LINK_MULTICAST_LEAVE:
            nx_link_multicast_leave(nx_ip_driver, device);
            break;
        case NX_LINK_DEFERRED_PROCESSING:
            nx_link_deferred_processing(nx_ip_driver, device);
            break;
        default:
            nx_ip_driver->nx_ip_driver_status = NX_UNHANDLED_COMMAND;
            break;
    }
}

static void nx_link_interface_attach(NX_IP_DRIVER *nx_ip_driver, device_driver_t *device)
{
    device->nx_driver->interface = nx_ip_driver->nx_ip_driver_interface;
    device->nx_driver->ip = nx_ip_driver->nx_ip_driver_ptr;
}

static void nx_link_initialize(NX_IP_DRIVER *nx_ip_driver, device_driver_t *device)
{
    device->state = NX_DRIVER_STATE_NOT_INITIALIZED;
    device->multicast_group_cnt = 0;

    if (hardware_initialize(device) != NX_SUCCESS) {
        nx_ip_driver->nx_ip_driver_status = NX_DRIVER_ERROR;
        return;
    }

    device->incomming_packet_pool = nx_ip_driver->nx_ip_driver_ptr->nx_ip_default_packet_pool;

    UINT interface_index = nx_ip_driver->nx_ip_driver_interface->nx_interface_index;

    nx_ip_interface_mtu_set(nx_ip_driver->nx_ip_driver_ptr, interface_index, (device->mtu - ETHERNET_FRAME_SIZE));

    nx_ip_interface_physical_address_set(nx_ip_driver->nx_ip_driver_ptr,
                                         interface_index,
                                         (uint32_t)((device->mac_address.b[0] << 8) | (device->mac_address.b[1])),
                                         (uint32_t)((device->mac_address.b[2] << 24) | (device->mac_address.b[3] << 16)
                                                    | (device->mac_address.b[4] << 8) | (device->mac_address.b[5])),
                                         NX_FALSE);

    nx_ip_interface_address_mapping_configure(nx_ip_driver->nx_ip_driver_ptr, interface_index, NX_TRUE);

    device->state = NX_DRIVER_STATE_INITIALIZED;
}

static void nx_link_enable(NX_IP_DRIVER *nx_ip_driver, device_driver_t *device)
{
    if (device->state < NX_DRIVER_STATE_INITIALIZED) {
        nx_ip_driver->nx_ip_driver_status = NX_DRIVER_ERROR;
        return;
    }

    if (device->state == NX_DRIVER_STATE_LINK_ENABLED) {
        nx_ip_driver->nx_ip_driver_status = NX_ALREADY_ENABLED;
        return;
    }

    if (hardware_enable(device) != NX_SUCCESS) {
        nx_ip_driver->nx_ip_driver_status = NX_DRIVER_ERROR;
        return;
    }

    if (device->link != ARM_ETH_LINK_UP) {
        hardware_check_link(device);
    }
    nx_ip_driver->nx_ip_driver_interface->nx_interface_link_up = (device->link == ARM_ETH_LINK_UP) ? NX_TRUE : NX_FALSE;

    device->state = NX_DRIVER_STATE_LINK_ENABLED;
}

static void nx_link_disable(NX_IP_DRIVER *nx_ip_driver, device_driver_t *device)
{
    if (device->state != NX_DRIVER_STATE_LINK_ENABLED) {
        // As documentation is not provided for what to do in this case, return an error as done by
        // Microsoft sample drivers at
        // https://github.com/azure-rtos/getting-started/blob/master/NXP/MIMXRT1050-EVKB/lib/
        // netx_driver/src/nx_driver_imxrt10xx.c#L592
        nx_ip_driver->nx_ip_driver_status = NX_DRIVER_ERROR;
        return;
    }

    if (hardware_disable(device) != NX_SUCCESS) {
        nx_ip_driver->nx_ip_driver_status = NX_DRIVER_ERROR;
        return;
    }

    if (device->link != ARM_ETH_LINK_DOWN) {
        hardware_check_link(device);
    }
    nx_ip_driver->nx_ip_driver_interface->nx_interface_link_up = (device->link == ARM_ETH_LINK_UP) ? NX_TRUE : NX_FALSE;
    device->state = NX_DRIVER_STATE_INITIALIZED;
}

static void nx_link_packet_send(NX_IP_DRIVER *nx_ip_driver, device_driver_t *device)
{
    if (device->state != NX_DRIVER_STATE_LINK_ENABLED) {
        nx_ip_driver->nx_ip_driver_status = NX_NOT_ENABLED;
        return;
    }

    NX_PACKET *outgoing_packet = nx_ip_driver->nx_ip_driver_packet;

    // Point to the begining of the Ethernet packet instead of its payload
    outgoing_packet->nx_packet_prepend_ptr = outgoing_packet->nx_packet_prepend_ptr - ETHERNET_FRAME_SIZE;

    // Ensure the size includes the Ethernet header
    outgoing_packet->nx_packet_length = outgoing_packet->nx_packet_length + ETHERNET_FRAME_SIZE;

    if (outgoing_packet->nx_packet_length > device->mtu) {
        remove_nx_packet_ethernet_header(outgoing_packet);
        nx_ip_driver->nx_ip_driver_status = NX_DRIVER_ERROR;
        return;
    }

    // Go back another 2 bytes (2 + 14 = 16 which is divisible by 4) to be at 4 byte alignment.
    uint32_t *ethernet_frame_ptr =
        (uint32_t *)(outgoing_packet->nx_packet_prepend_ptr - ETHERNET_FRAME_ALIGNMENT_BYTES_SIZE);

    add_ethernet_header(nx_ip_driver, ethernet_frame_ptr);

    device->outgoing_packet = outgoing_packet;

    UINT status = hardware_send_packet(outgoing_packet, device);

    if (status != NX_SUCCESS) {
        remove_nx_packet_ethernet_header(outgoing_packet);
        nx_ip_driver->nx_ip_driver_status = NX_DRIVER_ERROR;
    }

    // The packet is now in the HW buffer, can be released here.
    remove_nx_packet_ethernet_header(gs_device.outgoing_packet);
    nx_packet_transmit_release(gs_device.outgoing_packet);
}

static void nx_link_multicast_join(NX_IP_DRIVER *nx_ip_driver, device_driver_t *device)
{
    if (device->state != NX_DRIVER_STATE_LINK_ENABLED) {
        nx_ip_driver->nx_ip_driver_status = NX_NOT_ENABLED;
        return;
    }

    if (hardware_multicast_join(nx_ip_driver, device) != NX_SUCCESS) {
        nx_ip_driver->nx_ip_driver_status = NX_DRIVER_ERROR;
    }
}

static void nx_link_multicast_leave(NX_IP_DRIVER *nx_ip_driver, device_driver_t *device)
{
    if (device->state != NX_DRIVER_STATE_LINK_ENABLED) {
        nx_ip_driver->nx_ip_driver_status = NX_NOT_ENABLED;
        return;
    }

    if (hardware_multicast_leave(nx_ip_driver, device) != NX_SUCCESS) {
        nx_ip_driver->nx_ip_driver_status = NX_DRIVER_ERROR;
    }
}

static void nx_link_get_status(NX_IP_DRIVER *nx_ip_driver, device_driver_t *device)
{
    hardware_check_link(device);
    nx_ip_driver->nx_ip_driver_interface->nx_interface_link_up = (device->link == ARM_ETH_LINK_UP) ? NX_TRUE : NX_FALSE;
    *(nx_ip_driver->nx_ip_driver_return_ptr) = nx_ip_driver->nx_ip_driver_ptr->nx_ip_interface[0].nx_interface_link_up;
}

static void nx_link_deferred_processing(NX_IP_DRIVER *nx_ip_driver, device_driver_t *device)
{
    TX_INTERRUPT_SAVE_AREA
    TX_DISABLE

    uint8_t deferred_events = device->deferred_event;

    // Assert that no unexpected events are deferred
    assert((device->deferred_event & ~DEFERRED_EVENT_PACKET_RECEIVED) == 0);

    device->deferred_event = 0;

    TX_RESTORE

    if (deferred_events & DEFERRED_EVENT_PACKET_RECEIVED) {
        if (hardware_receive_packet(nx_ip_driver, device) != NX_SUCCESS) {
            nx_ip_driver->nx_ip_driver_status = NX_DRIVER_ERROR;
        }
    }
}

static bool transfer_to_netx(NX_IP_DRIVER *nx_ip_driver, NX_PACKET *incoming_packet)
{
    incoming_packet->nx_packet_ip_interface = nx_ip_driver->nx_ip_driver_interface;

    const uint32_t packet_type_msb =
        (uint32_t) * (incoming_packet->nx_packet_prepend_ptr + ETHERNET_ETHER_TYPE_MSB_POS);
    const uint32_t packet_type_lsb =
        (uint32_t) * (incoming_packet->nx_packet_prepend_ptr + ETHERNET_ETHER_TYPE_LSB_POS);
    const uint16_t packet_type = (uint16_t)((packet_type_msb << 8) | packet_type_lsb);

    // Point to the Ethernet payload
    // Note:  The length reported includes bytes after the packet as well as
    //        the Ethernet header. The actual packet length after the Ethernet
    //        header should be derived from the length in the IP header
    //        (lower 16 bits of the first 32-bit word).
    remove_nx_packet_ethernet_header(incoming_packet);

    switch (packet_type) {
        case ETHER_PACKET_TYPE_IP:
        case ETHER_PACKET_TYPE_IPV6:
            _nx_ip_packet_deferred_receive(nx_ip_driver->nx_ip_driver_ptr, incoming_packet);
            break;
        case ETHER_PACKET_TYPE_ARP:
            _nx_arp_packet_deferred_receive(nx_ip_driver->nx_ip_driver_ptr, incoming_packet);
            break;
        case ETHER_PACKET_TYPE_RARP:
            _nx_rarp_packet_deferred_receive(nx_ip_driver->nx_ip_driver_ptr, incoming_packet);
            break;
        default:
            return false;
    }

    return true;
}

static UINT hardware_initialize(device_driver_t *device)
{
    ARM_ETH_MAC_CAPABILITIES cap;

    device->phy = &ARM_Driver_ETH_PHY_(ETH_DRV_NUM);
    device->mac = &ARM_Driver_ETH_MAC_(ETH_DRV_NUM);

    device->link = ARM_ETH_LINK_DOWN;
    device->phy_ok = false;

    /* Get MAC capabilities */
    cap = device->mac->GetCapabilities();
    device->event_rx_frame = (cap.event_rx_frame) ? true : false;

    device->mtu = ETH_DRV_MTU_SIZE;

    if (device->mac->Initialize(on_event) == ARM_DRIVER_OK) {
        device->mac->PowerControl(ARM_POWER_FULL);
        device->mac->GetMacAddress(&device->mac_address);
        device->mac->Control(ARM_ETH_MAC_CONTROL_TX, 0);
        device->mac->Control(ARM_ETH_MAC_CONTROL_RX, 0);

        /* Initialize Physical Media Interface */
        if (device->phy->Initialize(device->mac->PHY_Read, device->mac->PHY_Write) == ARM_DRIVER_OK) {
            device->phy->PowerControl(ARM_POWER_FULL);
            device->phy->SetInterface(cap.media_interface);
            device->phy->SetMode(ARM_ETH_PHY_AUTO_NEGOTIATE);
            device->phy_ok = true;
            return NX_SUCCESS;
        }
    }
    return NX_DRIVER_ERROR;
}

static void hardware_check_link(device_driver_t *device)
{
    ARM_ETH_LINK_STATE link;

    if (!device->phy_ok) {
        return;
    }
    /* Check link status */
    link = device->phy->GetLinkState();
    if (link == device->link) {
        /* No change */
        return;
    }
    device->link = link;
    if (device->link == ARM_ETH_LINK_UP) {
        /* Start EMAC DMA */
        ARM_ETH_LINK_INFO info = device->phy->GetLinkInfo();
        uint32_t arg =
            info.speed << ARM_ETH_MAC_SPEED_Pos | info.duplex << ARM_ETH_MAC_DUPLEX_Pos | ARM_ETH_MAC_ADDRESS_BROADCAST;
        device->mac->Control(ARM_ETH_MAC_CONFIGURE, arg);
        device->mac->Control(ARM_ETH_MAC_CONTROL_TX, 1);
        device->mac->Control(ARM_ETH_MAC_CONTROL_RX, 1);
    } else {
        /* Stop EMAC DMA */
        device->mac->Control(ARM_ETH_MAC_CONTROL_TX, 0);
        device->mac->Control(ARM_ETH_MAC_CONTROL_RX, 0);
    }
}

static UINT hardware_enable(device_driver_t *device)
{
    device->mac->PowerControl(ARM_POWER_FULL);
    device->phy->PowerControl(ARM_POWER_FULL);
    return NX_SUCCESS;
}

static UINT hardware_disable(device_driver_t *device)
{
    device->phy->PowerControl(ARM_POWER_OFF);
    device->mac->PowerControl(ARM_POWER_OFF);
    return NX_SUCCESS;
}

static UINT hardware_multicast_join(NX_IP_DRIVER *nx_ip_driver, device_driver_t *device)
{
    int32_t status;
    uint8_t i;
    ARM_ETH_MAC_ADDR addr = {.b = {(uint8_t)(nx_ip_driver->nx_ip_driver_physical_address_msw >> 8),
                                   (uint8_t)(nx_ip_driver->nx_ip_driver_physical_address_msw),
                                   (uint8_t)(nx_ip_driver->nx_ip_driver_physical_address_lsw >> 24),
                                   (uint8_t)(nx_ip_driver->nx_ip_driver_physical_address_lsw >> 16),
                                   (uint8_t)(nx_ip_driver->nx_ip_driver_physical_address_lsw >> 8),
                                   (uint8_t)(nx_ip_driver->nx_ip_driver_physical_address_lsw)}};

    for (i = 0; i < device->multicast_group_cnt; i++) {
        if (ARM_ETH_MAC_ADDR_EQ(device->multicast_groups[i], addr)) {
            /* Already in group */
            return NX_SUCCESS;
        }
    }

    if (device->multicast_group_cnt == NX_MAX_MULTICAST_GROUPS) {
        /* Limit reached */
        return NX_DRIVER_ERROR;
    }

    device->multicast_groups[device->multicast_group_cnt++] = addr;
    status = device->mac->SetAddressFilter(device->multicast_groups, device->multicast_group_cnt);
    if (status != ARM_DRIVER_OK) {
        /* Couldn't update filter, maintain old list */
        device->multicast_group_cnt--;
        return NX_DRIVER_ERROR;
    }
    return NX_SUCCESS;
}

static UINT hardware_multicast_leave(NX_IP_DRIVER *nx_ip_driver, device_driver_t *device)
{
    int32_t status;
    uint8_t i;
    ARM_ETH_MAC_ADDR addr = {.b = {(uint8_t)(nx_ip_driver->nx_ip_driver_physical_address_msw >> 8),
                                   (uint8_t)(nx_ip_driver->nx_ip_driver_physical_address_msw),
                                   (uint8_t)(nx_ip_driver->nx_ip_driver_physical_address_lsw >> 24),
                                   (uint8_t)(nx_ip_driver->nx_ip_driver_physical_address_lsw >> 16),
                                   (uint8_t)(nx_ip_driver->nx_ip_driver_physical_address_lsw >> 8),
                                   (uint8_t)(nx_ip_driver->nx_ip_driver_physical_address_lsw)}};

    if (device->multicast_group_cnt == 0) {
        /* Address not in group */
        return NX_SUCCESS;
    }

    for (i = 0; i < device->multicast_group_cnt; i++) {
        if (ARM_ETH_MAC_ADDR_EQ(device->multicast_groups[i], addr)) {
            break;
        }
    }
    if (i == device->multicast_group_cnt) {
        /* Address not in group */
        return NX_SUCCESS;
    }

    device->multicast_group_cnt--;
    if (i != device->multicast_group_cnt) {
        /* Replace current address with the last in list, only if current is not the last */
        device->multicast_groups[i] = device->multicast_groups[device->multicast_group_cnt];
    }

    status = device->mac->SetAddressFilter(device->multicast_groups, device->multicast_group_cnt);
    if (status != ARM_DRIVER_OK) {
        /* Couldn't update filter, maintain old list */
        device->multicast_groups[device->multicast_group_cnt++] = addr;
        return NX_DRIVER_ERROR;
    }
    return NX_SUCCESS;
}

static UINT hardware_send_packet(NX_PACKET *outgoing_packet, device_driver_t *device)
{
    int32_t status;
    status = device->mac->SendFrame(outgoing_packet->nx_packet_prepend_ptr, outgoing_packet->nx_packet_length, 0);
    return (status == ARM_DRIVER_OK) ? NX_SUCCESS : NX_DRIVER_ERROR;
}

static UINT hardware_receive_packet(NX_IP_DRIVER *nx_ip_driver, device_driver_t *device)
{
    uint16_t len;
    NX_PACKET *incoming_packet = {0};

    len = device->mac->GetRxFrameSize();
    if (len == 0) {
        /* No packet available */
        return NX_NO_PACKET;
    }
    if (len > device->mtu) {
        /* Drop oversized packet */
        device->mac->ReadFrame(NULL, 0);
        return NX_NO_PACKET;
    }

    if (nx_packet_allocate(device->incomming_packet_pool, &incoming_packet, NX_RECEIVE_PACKET, NX_NO_WAIT)
        != NX_SUCCESS) {
        return NX_DRIVER_ERROR;
    }
    incoming_packet->nx_packet_prepend_ptr += ETHERNET_FRAME_ALIGNMENT_BYTES_SIZE;

    /* We are assuming the data will fit into this payload. */
    if (device->mac->ReadFrame(incoming_packet->nx_packet_prepend_ptr, len) < 0) {
        nx_packet_release(incoming_packet);
        return NX_DRIVER_ERROR;
    }

    incoming_packet->nx_packet_length = len;
    incoming_packet->nx_packet_append_ptr = incoming_packet->nx_packet_prepend_ptr + incoming_packet->nx_packet_length;

    if (transfer_to_netx(nx_ip_driver, incoming_packet) == false) {
        nx_packet_release(incoming_packet);
    }

    /* Check if there are more packets to receive */
    if (device->mac->GetRxFrameSize()) {
        gs_device.deferred_event |= DEFERRED_EVENT_PACKET_RECEIVED;
        _nx_ip_driver_deferred_processing(gs_device.nx_driver->ip);
    }

    return NX_SUCCESS;
}

static void on_event(uint32_t event)
{
    switch (event) {
        case ARM_ETH_MAC_EVENT_RX_FRAME:
            gs_device.deferred_event |= DEFERRED_EVENT_PACKET_RECEIVED;
            _nx_ip_driver_deferred_processing(gs_device.nx_driver->ip);
            break;
        default:
            break;
    }
}

static void add_ethernet_header(NX_IP_DRIVER *nx_ip_driver, uint32_t *ethernet_frame_ptr)
{
    ethernet_frame_ptr[0] = nx_ip_driver->nx_ip_driver_physical_address_msw;
    ethernet_frame_ptr[1] = nx_ip_driver->nx_ip_driver_physical_address_lsw;
    ethernet_frame_ptr[2] = (nx_ip_driver->nx_ip_driver_interface->nx_interface_physical_address_msw << 16)
                            | (nx_ip_driver->nx_ip_driver_interface->nx_interface_physical_address_lsw >> 16);
    ethernet_frame_ptr[3] = (nx_ip_driver->nx_ip_driver_interface->nx_interface_physical_address_lsw << 16);

    if ((nx_ip_driver->nx_ip_driver_command == NX_LINK_ARP_SEND)
        || (nx_ip_driver->nx_ip_driver_command == NX_LINK_ARP_RESPONSE_SEND)) {
        ethernet_frame_ptr[3] |= ETHER_PACKET_TYPE_ARP;
    } else if (nx_ip_driver->nx_ip_driver_command == NX_LINK_RARP_SEND) {
        ethernet_frame_ptr[3] |= ETHER_PACKET_TYPE_RARP;
    } else if (nx_ip_driver->nx_ip_driver_packet->nx_packet_ip_version == NX_IP_VERSION_V6) {
        ethernet_frame_ptr[3] |= ETHER_PACKET_TYPE_IPV6;
    } else {
        ethernet_frame_ptr[3] |= ETHER_PACKET_TYPE_IP;
    }

    /* Endian swapping if NX_LITTLE_ENDIAN is defined. */
    NX_CHANGE_ULONG_ENDIAN(ethernet_frame_ptr[0]);
    NX_CHANGE_ULONG_ENDIAN(ethernet_frame_ptr[1]);
    NX_CHANGE_ULONG_ENDIAN(ethernet_frame_ptr[2]);
    NX_CHANGE_ULONG_ENDIAN(ethernet_frame_ptr[3]);
}

static inline void remove_nx_packet_ethernet_header(NX_PACKET *packet)
{
    packet->nx_packet_prepend_ptr += ETHERNET_FRAME_SIZE;
    packet->nx_packet_length -= ETHERNET_FRAME_SIZE;
}
